﻿using System;
using System.Xml;

using Yahoo.Yui.Compressor;

namespace Pcubed.Shuie
{
  /// <summary>
  /// ShuieXml encapsulates a SharePoint User Interface Extension.
  /// </summary>
  class ShuieXml : XmlDocument
  {
    #region CONSTANTS
    private const string STR_Shuie = "shuie";
    private const string STR_Minify = "minify";
    private const string STR_Code = "code";
    private const string STR_Output = "output";
    private const string STR_Id = "id";
    private const string STR_Order = "order";
    private const string STR_Active = "active";
    private const string STR_True = "true";
    private const string STR_Title = "title";
    private const string STR_Styles = "styles";
    private const string STR_Css = "css";
    private const string STR_Mediatype = "mediatype";
    private const string STR_Screen = "screen";
    private const string STR_Json = "json";
    private const string STR_Contexts = "contexts";
    private const string STR_Context = "context";
    private const string STR_SiteUri = "siteUri";
    private const string STR_WebUri = "webUri";
    private const string STR_UserName = "userName";
    private const string STR_UserLogin = "userLogin";
    private const string STR_BaseTemplate = "baseTemplate";
    private const string STR_ListTitle = "listTitle";
    private const string STR_WebTitle = "webTitle";
    private const string STR_Display = "display";
    private const string STR_Edit = "edit";
    private const string STR_New = "new";
    private const string STR_Invalid = "invalid";
    private const string STR_UserIsWebAdmin = "userIsWebAdmin";
    private const string STR_UserIsSiteAdmin = "userIsSiteAdmin";
    private const string STR_Javascript = "javascript";
    private const string STR_PageMode = "pageMode";
    #endregion

    /// <summary>
    /// Identifies an instance of a ShUIE extension
    /// </summary>
    public Guid Id
    {
      get
      {
        XmlNode rootNode = SelectSingleNode("/shuie");

        return (
          null != rootNode && null != rootNode.Attributes[STR_Id] &&
          !String.IsNullOrEmpty(rootNode.Attributes[STR_Id].Value)
        ) ?
          new Guid(SelectSingleNode("/shuie").Attributes[STR_Id].Value) :
          Guid.Empty;
      }

      set
      {
        XmlNode rootNode = SelectSingleNode("/shuie");

        if (rootNode != null)
        {
          if (rootNode.Attributes[STR_Id] == null)
          {
            XmlAttribute id = CreateAttribute(STR_Id);
            rootNode.Attributes.Append(id);
          }
          rootNode.Attributes[STR_Id].Value = value.ToString();
        }
        else
          throw new Exception("ShUIE entry is not initialised");
      }
    }

    /// <summary>
    /// Determines the render sequence, 1 = first to render and higher
    /// numbers = later rendering. Multiple entries of the same value
    /// have no guaranteed rendering position.
    /// </summary>
    public Int32 Order
    {
      get
      {
        XmlNode rootNode = SelectSingleNode("/shuie");
        Int32 order = Int32.MaxValue;

        if (
          null != rootNode &&
          null != rootNode.Attributes[STR_Order] &&
          !String.IsNullOrEmpty(rootNode.Attributes[STR_Order].Value)
        )
          return (Int32.TryParse(rootNode.Attributes[STR_Order].Value, out order)) ?
            order :
            Int32.MaxValue;

        return order;
      }

      set
      {
        XmlNode rootNode = SelectSingleNode("/shuie");

        if (rootNode != null)
        {
          XmlAttribute orderNode = rootNode.Attributes[STR_Order];
          if (orderNode == null)
          {
            orderNode = CreateAttribute(STR_Order);
            rootNode.Attributes.Append(orderNode);
          }
          orderNode.Value = value.ToString();
        }
        else
          throw new Exception("ShUIE entry is not initialised");
      }
    }

    /// <summary>
    /// Indicates whether this ShUIE extension is currently active and will be
    /// rendered.
    /// </summary>
    public Boolean IsActive
    {
      get
      {
        XmlNode rootNode = SelectSingleNode("/shuie");
        Boolean isActive = false;

        if (
          rootNode != null &&
          rootNode.Attributes[STR_Active] != null &&
          rootNode.Attributes[STR_Active].Value != null
        )
          if (Boolean.TryParse(rootNode.Attributes[STR_Active].Value, out isActive))
            return isActive;

        return false;
      }

      set
      {
        XmlNode rootNode = SelectSingleNode("/shuie");

        if (rootNode != null)
          if (rootNode.Attributes[STR_Active] != null)
            rootNode.Attributes[STR_Active].Value = value.ToString().ToLowerInvariant();
          else
          {
            XmlAttribute active = CreateAttribute(STR_Active);
            active.Value = STR_True;
            rootNode.Attributes.Append(active);
          }
        else
          throw new Exception("ShUIE entry is not initialised");
      }
    }

    /// <summary>
    /// The title given to this ShUIE extension
    /// </summary>
    public String Title
    {
      get
      {
        XmlNode jsonNode = SelectSingleNode("/shuie/title");

        return (
          null != jsonNode &&
          null != jsonNode.FirstChild &&
          !String.IsNullOrEmpty(jsonNode.FirstChild.Value)
        ) ?
          jsonNode.FirstChild.Value :
          String.Empty;
      }

      set
      {
        XmlNode rootNode = SelectSingleNode("/shuie");

        if (rootNode != null)
        {
          XmlNode titleNode = rootNode.SelectSingleNode(STR_Title);

          if (titleNode == null)
          {
            titleNode = CreateElement(STR_Title);
            rootNode.AppendChild(titleNode);
          }

          titleNode.RemoveAll();
          titleNode.AppendChild(CreateCDataSection(value));
        }
        else
          throw new Exception("ShUIE entry is not initialised");
      }
    }

    /// <summary>
    /// Creates a new instance of ShuieXml
    /// </summary>
    /// <param name="title">The title of this extension</param>
    public ShuieXml(String title)
    {
      XmlNode rootNode = CreateElement(STR_Shuie);
      AppendChild(rootNode);

      XmlAttribute id = CreateAttribute(STR_Id);
      id.Value = Guid.NewGuid().ToString();
      rootNode.Attributes.Append(id);

      XmlAttribute active = CreateAttribute(STR_Active);
      active.Value = STR_True;
      rootNode.Attributes.Append(active);

      XmlAttribute orderNode = CreateAttribute(STR_Order);
      orderNode.Value = Int32.MaxValue.ToString();
      rootNode.Attributes.Append(orderNode);

      XmlNode titleNode = CreateElement(STR_Title);
      rootNode.AppendChild(titleNode);

      titleNode.AppendChild(CreateCDataSection(title));
    }

    #region Context
    /// <summary>
    /// Creates a new context, that when matched will render this ShUIE extension
    /// </summary>
    /// <param name="siteUri">The URI of the SharePoint site or parent site</param>
    /// <param name="webUri">The URI of the SharePoint web</param>
    /// <param name="webTitle">The title of the SharePoint web</param>
    /// <param name="listTitle">The title of the SharePoint list</param>
    /// <param name="baseTemplate">The title of the template used to provision
    /// the site or web</param>
    /// <param name="userLogin">The AD name (domain\username) of the user</param>
    /// <param name="userName">The username</param>
    /// <param name="userIsSiteAdmin">Whether the user is a site admin</param>
    /// <param name="userIsWebAdmin">Whether the user is a web admin</param>
    /// <param name="invalidValue">Whether the page is "invalid", therefore not
    /// a list item page</param>
    /// <param name="newValue">Whether the list item page is a "new" page</param>
    /// <param name="ediValue">Whether the list item page is an "edit" page</param>
    /// <param name="displayValue">Whether the list item page is a "display" page</param>
    public void AddContext(ShuieContext context)
    {
      XmlNode contextsNode;

      if (GetElementsByTagName(STR_Contexts).Count == 0)
      {
        contextsNode = CreateElement(STR_Contexts);
        SelectSingleNode(STR_Shuie).AppendChild(contextsNode);
      }
      else
        contextsNode = GetElementsByTagName(STR_Contexts).Item(0);

      XmlNode contextNode = CreateElement(STR_Context);

      XmlAttribute active = CreateAttribute(STR_Active);
      active.Value = STR_True;
      contextNode.Attributes.Append(active);

      XmlAttribute id = CreateAttribute(STR_Id);
      id.Value = Guid.NewGuid().ToString();
      contextNode.Attributes.Append(id);
      contextsNode.AppendChild(contextNode);

      if (context.SiteUri != null && !context.SiteUri.Equals(String.Empty))
        SetContextNode(contextNode.Attributes[STR_Id].Value, STR_SiteUri, context.SiteUri);

      if (context.WebUri != null && !context.WebUri.Equals(String.Empty))
        SetContextNode(id.Value, STR_WebUri, context.WebUri);

      if (context.WebTitle != null && !context.WebTitle.Equals(String.Empty))
        SetContextNode(id.Value, STR_WebTitle, context.WebTitle);

      if (context.ListTitle != null && !context.ListTitle.Equals(String.Empty))
        SetContextNode(id.Value, STR_ListTitle, context.ListTitle);

      if (context.BaseTemplate != null && !context.BaseTemplate.Equals(String.Empty))
        SetContextNode(id.Value, STR_BaseTemplate, context.BaseTemplate);

      if (context.UserLogin != null && !context.UserLogin.Equals(String.Empty))
        SetContextNode(id.Value, STR_UserLogin, context.UserLogin);

      if (context.UserName != null && !context.UserName.Equals(String.Empty))
        SetContextNode(id.Value, STR_UserName, context.UserName);

      if (context.UserIsSiteAdmin)
        SetContextNode(id.Value, STR_UserIsSiteAdmin, context.UserIsSiteAdmin);

      if (context.UserIsWebAdmin)
        SetContextNode(id.Value, STR_UserIsWebAdmin, context.UserIsWebAdmin);

      if (context.IsInvalidMode)
        SetContextPageMode(id.Value, STR_Invalid, context.IsInvalidMode);

      if (context.IsNewMode)
        SetContextPageMode(id.Value, STR_New, context.IsNewMode);

      if (context.IsEditMode)
        SetContextPageMode(id.Value, STR_Edit, context.IsEditMode);

      if (context.IsDisplayMode)
        SetContextPageMode(id.Value, STR_Display, context.IsDisplayMode);
    }

    /// <summary>
    /// Modifies the value of the page mode
    /// </summary>
    /// <param name="contextId">The identifier for the context to modify</param>
    /// <param name="pageMode">The page mode (invalid|new|edit|display) to modify</param>
    /// <param name="value">Whether this page mode is active or inactive</param>
    private void SetContextPageMode(String contextId, String pageMode, Boolean value)
    {
      if (contextId != null
        && !contextId.Equals(String.Empty)
        && pageMode != null
        && !pageMode.Equals(String.Empty))
      {
        XmlNode context = SelectSingleNode(
          String.Format("/shuie/contexts/context[attribute::id='{0}']", contextId)
        );
        XmlNode pageModeWrap;
        XmlNode node;

        if (context.SelectNodes(STR_PageMode).Count == 0)
        {
          pageModeWrap = CreateElement(STR_PageMode);
          context.AppendChild(pageModeWrap);
        }
        else
          pageModeWrap = context.SelectSingleNode(STR_PageMode);

        if (pageModeWrap.SelectNodes(pageMode).Count == 0)
        {
          node = CreateElement(pageMode);
          pageModeWrap.AppendChild(node);
          node.InnerText = value.ToString().ToLowerInvariant();
        }
        else
        {
          node = pageModeWrap.SelectSingleNode(pageMode);
          node.InnerText = value.ToString().ToLowerInvariant();
        }
      }
    }

    /// <summary>
    /// Sets the value of a context node for a given context
    /// </summary>
    /// <param name="contextId">The identifier for the context to modify</param>
    /// <param name="nodeName">The property to modify</param>
    /// <param name="value">The value to set for this property</param>
    public void SetContextNode(String contextId, String nodeName, String value)
    {
      if (contextId != null
        && !contextId.Equals(String.Empty)
        && nodeName != null
        && !nodeName.Equals(String.Empty))
      {
        XmlNode context = SelectSingleNode(
          String.Format("/shuie/contexts/context[attribute::id='{0}']", contextId)
        );
        XmlNode node;
        XmlNode cdataValue;

        if (context.SelectNodes(nodeName).Count == 0)
        {
          node = CreateElement(nodeName);
          context.AppendChild(node);
          cdataValue = CreateCDataSection(value);
        }
        else
        {
          if (value == null || value.Equals(String.Empty))
          {
            context.RemoveChild(context.SelectSingleNode(nodeName));
            return;
          }
          else
          {
            node = context.SelectSingleNode(nodeName);
            cdataValue = node.FirstChild;
            cdataValue.Value = value;
          }
        }

        node.AppendChild(cdataValue);
      }
    }

    /// <summary>
    /// Sets the value of a context node for a given context
    /// </summary>
    /// <param name="contextId">The identifier for the context to modify</param>
    /// <param name="nodeName">The property to modify</param>
    /// <param name="value">The value to set for this property</param>
    private void SetContextNode(String contextId, String nodeName, Boolean value)
    {
      SetContextNode(contextId, nodeName, value.ToString().ToLowerInvariant());
    }

    /// <summary>
    /// Checks a given context and returns true if in this context this ShUIE
    /// extension would be rendered
    /// </summary>
    /// <param name="siteUri">The URI of the SharePoint site or parent site</param>
    /// <param name="webUri">The URI of the SharePoint web</param>
    /// <param name="webTitle">The title of the SharePoint web</param>
    /// <param name="listTitle">The title of the SharePoint list</param>
    /// <param name="baseTemplate">The title of the template used to provision
    /// the site or web</param>
    /// <param name="userLogin">The AD name (domain\username) of the user</param>
    /// <param name="userName">The username</param>
    /// <param name="userIsSiteAdmin">Whether the user is a site admin</param>
    /// <param name="userIsWebAdmin">Whether the user is a web admin</param>
    /// <param name="invalidValue">Whether the page is "invalid", therefore not
    /// a list item page</param>
    /// <param name="newValue">Whether the list item page is a "new" page</param>
    /// <param name="ediValue">Whether the list item page is an "edit" page</param>
    /// <param name="displayValue">Whether the list item page is a "display" page</param>
    /// <returns></returns>
    public Boolean CheckContext(ShuieContext context)
    {
      Boolean canRun = true;

      foreach (XmlNode contextNode in SelectNodes("/shuie/contexts/context"))
      {
        foreach (XmlNode field in contextNode.ChildNodes)
        {
          switch (field.Name)
          {
            case STR_SiteUri:
              canRun = MatchContextValue(context.SiteUri, field);
              break;
            case STR_WebUri:
              canRun = MatchContextValue(context.WebUri, field);
              break;
            case STR_WebTitle:
              canRun = MatchContextValue(context.WebTitle, field);
              break;
            case STR_ListTitle:
              canRun = MatchContextValue(context.ListTitle, field);
              break;
            case STR_BaseTemplate:
              canRun = MatchContextValue(context.BaseTemplate, field);
              break;
            case STR_UserLogin:
              canRun = MatchContextValue(context.UserLogin, field);
              break;
            case STR_UserName:
              canRun = MatchContextValue(context.UserName, field);
              break;
            case STR_UserIsSiteAdmin:
              canRun = MatchContextValue(context.UserIsSiteAdmin, field);
              break;
            case STR_UserIsWebAdmin:
              canRun = MatchContextValue(context.UserIsWebAdmin, field);
              break;
            case STR_PageMode:
              foreach (XmlNode pageModeNode in field.ChildNodes)
              {
                switch (pageModeNode.Name)
                {
                  case STR_Invalid:
                    canRun = MatchContextValue(context.IsInvalidMode, pageModeNode);
                    break;
                  case STR_New:
                    canRun = MatchContextValue(context.IsNewMode, pageModeNode);
                    break;
                  case STR_Edit:
                    canRun = MatchContextValue(context.IsEditMode, pageModeNode);
                    break;
                  case STR_Display:
                    canRun = MatchContextValue(context.IsDisplayMode, pageModeNode);
                    break;
                  default:
                    break;
                }

                if (canRun)
                  break;
              }
              break;
            default:
              break;
          }

          //
          // If there is one field in the current context that is false then
          // this context is false
          //
          if (!canRun)
            break;
        }

        //
        // If there is one context that is true then the code should run here
        //
        if (canRun)
          break;
      }

      return canRun;
    }

    private static Boolean MatchContextValue(String environmentValue, XmlNode configNode)
    {
      return !(
        /* NO environemnt value, but config value, so NOK */
        ((environmentValue == null || environmentValue.Equals(String.Empty))
          && configNode.FirstChild != null && configNode.FirstChild.Value != null)
        /* both exist, but are different, so NOK */
        || ((environmentValue != null || !environmentValue.Equals(String.Empty))
          && configNode.FirstChild != null && configNode.FirstChild.Value != null
          && !configNode.FirstChild.Value.ToLowerInvariant().Equals(environmentValue.ToLowerInvariant())));
    }

    private static Boolean MatchContextValue(Boolean environmentValue, XmlNode configNode)
    {
      Boolean configValue = (configNode.FirstChild != null && configNode.FirstChild.Value != null) ?
        Convert.ToBoolean(configNode.FirstChild.Value) : false;

      return !(configValue.Equals(true) && environmentValue.Equals(false));
    }
    #endregion

    #region JavaScript
    /// <summary>
    /// Sets the script that this ShUIE extension will render
    /// </summary>
    /// <param name="code">The JavaScript code to render</param>
    /// <param name="minify">Whether to minift the JavaScript before rendering</param>
    /// <returns>Any warnings that might occured</returns>
    public String SetJavaScript(String code, Boolean minify)
    {
      String warnings = String.Empty;

      XmlNode javaScriptNode = SelectSingleNode("/shuie/javascript");
      if (javaScriptNode == null)
      {
        javaScriptNode = CreateElement(STR_Javascript);
        SelectSingleNode("/shuie").AppendChild(javaScriptNode);
      }

      XmlAttribute minifyAttr = javaScriptNode.Attributes[STR_Minify];
      if (minifyAttr == null)
      {
        minifyAttr = CreateAttribute(STR_Minify);
        javaScriptNode.Attributes.Append(minifyAttr);
      }
      minifyAttr.Value = minify.ToString().ToLowerInvariant();

      XmlNode codeNode = javaScriptNode.SelectSingleNode(STR_Code);
      if (codeNode == null)
      {
        codeNode = CreateElement(STR_Code);
        javaScriptNode.AppendChild(codeNode);
      }
      codeNode.RemoveAll();
      codeNode.AppendChild(CreateCDataSection(code));

      XmlNode ouputNode = javaScriptNode.SelectSingleNode(STR_Output);
      if (ouputNode == null)
      {
        ouputNode = CreateElement(STR_Output);
        javaScriptNode.AppendChild(ouputNode);
      }
      ouputNode.RemoveAll();

      if (minify)
        try
        {
          ouputNode.AppendChild(CreateCDataSection(MinifyJs(code)));
        }
        catch (Exception ex)
        {
          warnings += String.Format("JavaScript code failed minification!{0}{1}", Environment.NewLine, ex.Message);
          ouputNode.AppendChild(CreateCDataSection(code));
        }
      else
        ouputNode.AppendChild(CreateCDataSection(code));

      return warnings;
    }


    public Boolean GetJavaScriptMinify()
    {
      XmlNode jsonNode = SelectSingleNode("/shuie/javascript");

      if (null != jsonNode)
      {
        XmlAttribute minifyAttr = jsonNode.Attributes[STR_Minify];

        return (null != minifyAttr
                && !String.IsNullOrEmpty(minifyAttr.Value)
                && Convert.ToBoolean(minifyAttr.Value).Equals(true)) ?
          true :
          false;
      }
      else
        return false;
    }

    public String GetJavaScriptCode()
    {
      XmlNode javaScriptNode = SelectSingleNode("/shuie/javascript/code");

      return (null != javaScriptNode && null != javaScriptNode.FirstChild && !String.IsNullOrEmpty(javaScriptNode.FirstChild.Value)) ?
        javaScriptNode.FirstChild.Value :
        String.Empty;
    }

    public String GetJavaScriptOutput()
    {
      XmlNode javaScriptNode = SelectSingleNode("/shuie/javascript/output");

      return (null != javaScriptNode && null != javaScriptNode.FirstChild && !String.IsNullOrEmpty(javaScriptNode.FirstChild.Value)) ?
        javaScriptNode.FirstChild.Value :
        String.Empty;
    }


    private void DeleteJavaScriptOutput()
    {
      XmlNode javaScriptNode = SelectSingleNode("/shuie/javascript");

      if(null != javaScriptNode && javaScriptNode.ChildNodes.Count > 0)
      {
        XmlNode outputNode = javaScriptNode.SelectSingleNode(STR_Output);
        if (outputNode != null)
          javaScriptNode.RemoveChild(outputNode);
      }
    }
    #endregion

    #region Styles
    public String SetCss(String mediaType, String code, Boolean minify)
    {
      String warnings = String.Empty;

      XmlNode stylesNode = SelectSingleNode("/shuie/styles");
      if (stylesNode == null)
      {
        stylesNode = CreateElement(STR_Styles);
        SelectSingleNode(STR_Shuie).AppendChild(stylesNode);
      }

      XmlNode cssNode = stylesNode.SelectSingleNode(String.Format("//css[@mediatype='{0}']", mediaType));
      if (cssNode == null)
      {
        cssNode = CreateElement(STR_Css);

        XmlAttribute minifyAttr = cssNode.Attributes[STR_Minify];
        if (minifyAttr == null)
        {
          minifyAttr = CreateAttribute(STR_Minify);
          cssNode.Attributes.Append(minifyAttr);
        }
        minifyAttr.Value = minify.ToString().ToLowerInvariant();

        XmlAttribute mediaTypeAttr = cssNode.Attributes[STR_Mediatype];
        if (mediaTypeAttr == null)
        {
          mediaTypeAttr = CreateAttribute(STR_Mediatype);
          cssNode.Attributes.Append(mediaTypeAttr);
        }
        mediaTypeAttr.Value = mediaType;

        stylesNode.AppendChild(cssNode);
      }

      XmlNode codeNode = cssNode.SelectSingleNode(STR_Code);
      if (codeNode == null)
      {
        codeNode = CreateElement(STR_Code);
        cssNode.AppendChild(codeNode);
      }
      codeNode.RemoveAll();
      codeNode.AppendChild(CreateCDataSection(code));

      XmlNode ouputNode = cssNode.SelectSingleNode(STR_Output);
      if (ouputNode == null)
      {
        ouputNode = CreateElement(STR_Output);
        cssNode.AppendChild(ouputNode);
      }
      ouputNode.RemoveAll();

      if (minify)
        try
        {
          ouputNode.AppendChild(CreateCDataSection(MinifyCss(code)));
        }
        catch (Exception ex)
        {
          warnings += String.Format("CSS code failed minification!{0}{1}", Environment.NewLine, ex.Message);

          ouputNode.AppendChild(CreateCDataSection(code));
        }
      else
        ouputNode.AppendChild(CreateCDataSection(code));

      return warnings;
    }

    public String[] GetMediaTypes()
    {
      XmlNode stylesNode = SelectSingleNode("/shuie/styles");

      if (stylesNode != null)
      {
        String[] mediaTypesList = new String[stylesNode.ChildNodes.Count];
        Int32 ii = 0;
        foreach (XmlNode cssNode in SelectNodes("/shuie/styles/css"))
        {
          mediaTypesList[ii] =
            (cssNode.Attributes[STR_Mediatype] == null && cssNode.Attributes[STR_Mediatype].Value == null) ?
            STR_Screen :
            cssNode.Attributes[STR_Mediatype].Value;

          ii++;
        }

        return mediaTypesList;
      }
      else
        return new String[] { };
    }

    public Boolean GetCssMinify(String mediaType)
    {
      XmlNode cssNode = SelectSingleNode(String.Format("/shuie/styles/css[attribute::mediatype='{0}']", mediaType));

      if (null != cssNode)
      {
        XmlAttribute minifyAttr = cssNode.Attributes[STR_Minify];

        return (null != minifyAttr
                && !String.IsNullOrEmpty(minifyAttr.Value)
                && Convert.ToBoolean(minifyAttr.Value).Equals(true)) ?
          true :
          false;
      }
      else
        return false;
    }

    public String GetCssCode(String mediaType)
    {
      XmlNode cssNode = SelectSingleNode(String.Format("/shuie/styles/css[attribute::mediatype='{0}']/code", mediaType));

      return (null != cssNode && null != cssNode.FirstChild && !String.IsNullOrEmpty(cssNode.FirstChild.Value)) ?
        cssNode.FirstChild.Value :
        String.Empty;
    }

    public String GetCssOutput(String mediaType)
    {
      XmlNode cssNode = SelectSingleNode(String.Format("/shuie/styles/css[attribute::mediatype='{0}']/output", mediaType));

      return (null != cssNode && null != cssNode.FirstChild && !String.IsNullOrEmpty(cssNode.FirstChild.Value)) ?
        cssNode.FirstChild.Value :
        String.Empty;
    }

    private void DeleteCssOutput(String mediaType)
    {
      XmlNode cssNode = SelectSingleNode(String.Format("/shuie/styles/css[attribute::mediatype='{0}']", mediaType));

      if (null != cssNode && cssNode.ChildNodes.Count > 0)
      {
        XmlNode outputNode = cssNode.SelectSingleNode(STR_Output);
        if (outputNode != null)
          cssNode.RemoveChild(outputNode);
      }
    }
    #endregion

    #region Json
    public String SetJson(String code, Boolean minify)
    {
      String warnings = String.Empty;

      XmlNode jsonNode = SelectSingleNode("/shuie/json");
      if (jsonNode == null)
      {
        jsonNode = CreateElement(STR_Json);
        SelectSingleNode(STR_Shuie).AppendChild(jsonNode);
      }

      XmlAttribute minifyAttr = jsonNode.Attributes[STR_Minify];
      if (jsonNode.Attributes[STR_Minify] == null)
      {
        minifyAttr = CreateAttribute(STR_Minify);
        jsonNode.Attributes.Append(minifyAttr);
      }
      minifyAttr.Value = minify.ToString().ToLowerInvariant();

      XmlNode codeNode = jsonNode.SelectSingleNode(STR_Code);
      if (codeNode == null)
      {
        codeNode = CreateElement(STR_Code);
        jsonNode.AppendChild(codeNode);
      }
      codeNode.RemoveAll();
      codeNode.AppendChild(CreateCDataSection(code));

      XmlNode ouputNode = jsonNode.SelectSingleNode(STR_Output);
      if (ouputNode == null)
      {
        ouputNode = CreateElement(STR_Output);
        jsonNode.AppendChild(ouputNode);
      }
      ouputNode.RemoveAll();

      if (minify)
      {
        try
        {
          ouputNode.AppendChild(CreateCDataSection(MinifyJs(code)));
        }
        catch (Exception ex)
        {
          warnings += String.Format("JSON code failed minification!{0}{1}", Environment.NewLine, ex.Message);

          ouputNode.AppendChild(CreateCDataSection(code));
        }
      }
      else
        ouputNode.AppendChild(CreateCDataSection(code));

      return warnings;
    }

    public Boolean GetJsonMinify()
    {
      XmlNode jsonNode = SelectSingleNode("/shuie/json");

      if (null != jsonNode)
      {
        XmlAttribute minifyAttr = jsonNode.Attributes[STR_Minify];

        return (
          null != minifyAttr
          && !String.IsNullOrEmpty(minifyAttr.Value)
          && Convert.ToBoolean(minifyAttr.Value).Equals(true)
        );
      }
      else
        return false;
    }

    public String GetJsonCode()
    {
      XmlNode jsonNode = SelectSingleNode("/shuie/json/code");

      return (null != jsonNode && null != jsonNode.FirstChild && !String.IsNullOrEmpty(jsonNode.FirstChild.Value)) ?
        jsonNode.FirstChild.Value :
        String.Empty;
    }

    public String GetJsonOutput()
    {
      XmlNode jsonNode = SelectSingleNode("/shuie/json/output");

      return (null != jsonNode && null != jsonNode.FirstChild && !String.IsNullOrEmpty(jsonNode.FirstChild.Value)) ?
          jsonNode.FirstChild.Value :
          String.Empty;
    }

    private void DeleteJsonOutput()
    {
      XmlNode jsonNode = SelectSingleNode("/shuie/json");

      if (null != jsonNode && jsonNode.ChildNodes.Count > 0)
      {
        XmlNode outputNode = jsonNode.SelectSingleNode(STR_Output);
        if (outputNode != null)
          jsonNode.RemoveChild(outputNode);
      }
    }
    #endregion

    public ShuieXml(XmlNameTable nt) : base(nt) { }
    protected internal ShuieXml(XmlImplementation imp) : base(imp) { }
    public ShuieXml() { }

    #region Utilities
    public String UpdateOutputs()
    {
      String warnings = String.Empty;

      String javaScript = GetJavaScriptCode();
      if (javaScript != null && !javaScript.Equals(String.Empty))
        warnings += SetJavaScript(javaScript, GetJavaScriptMinify());

      Array.ForEach(GetMediaTypes(), mediaType =>
      {
        if (GetCssCode(mediaType) != null)
          warnings += SetCss(mediaType, GetCssCode(mediaType), GetCssMinify(mediaType));
      });

      String json = GetJsonCode();
      if (json != null && !json.Equals(String.Empty))
        warnings += SetJson(json, GetJsonMinify());

      return warnings;
    }

    public void DeleteOutputs()
    {
      DeleteJavaScriptOutput();

      Array.ForEach(GetMediaTypes(), mediaType =>
      {
        DeleteCssOutput(mediaType);
      });

      DeleteJsonOutput();
    }

    private static String MinifyJs(String js)
    {
      return JavaScriptCompressor.Compress(js, false);
    }

    private static String MinifyCss(String css)
    {
      return CssCompressor.Compress(css);
    }
    #endregion
  }
}
